package mine;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.concurrent.atomic.*;

import javax.swing.*;

@SuppressWarnings("serial")
public class MineBox extends JButton{
	public static MainWindow mw;
	public static AtomicBoolean isWin;
	
	public static final Insets inset = new Insets(1, 1, 1, 1);
	public static final int MINE = 0x1000, OPEN = 0x2000, COMPLETE = 0x4000, FLAG = 0x8000;
	public static int flags, opens, empties;
	public static boolean start;
	private AtomicBoolean lock;
	private int row, column, status, neighbors;

	
	public static void setBoxes(MainWindow w){
		mw = w;
		
		empties = w.rows * w.columns - w.mines;
		flags = 0; opens = 0;
		start = false; isWin = new AtomicBoolean();
	}
	
	public MineBox(int r, int c){
		lock = new AtomicBoolean();
		row = r; column = c; status = 0;
		
		if(r == 0 || r == mw.rows - 1){
			if(c == 0 || c == mw.columns - 1){
				neighbors = 3;
			}else{
				neighbors = 5;
			}
		}else{
			if(c == 0 || c == mw.columns - 1){
				neighbors = 5;
			}else{
				neighbors = 8;
			}
		}
		
		int fontSize = getHeight() <= getWidth() ? getHeight() : getWidth();
		setFont(new Font("Arial", Font.PLAIN, fontSize - 4));
		setOpaque(true);
		setMargin(inset);
		
		addMouseListener(new MouseListener(){
			@Override
			public void mouseClicked(MouseEvent e) {
				
			}
			@Override
			public void mousePressed(MouseEvent e) {
				if(e.getButton() == MouseEvent.BUTTON1){
					if(!start) initializeMap();
					if(!isComplete()){
						if(isOpen()){
							openNeighborTip();
						}else open();
					}
				}else if(e.getButton() == MouseEvent.BUTTON3){
					if(isFlag()){
						clearFlag();
					}else if(!isOpen()){
						setFlag();
					}
				}
			}
			@Override
			public void mouseReleased(MouseEvent e) {
				
			}
			@Override
			public void mouseEntered(MouseEvent e) {
				
			}
			@Override
			public void mouseExited(MouseEvent e) {
				
			}
			
		});

		addComponentListener(new ComponentListener(){
			@Override
			public void componentResized(ComponentEvent e) {
				int fontSize = getHeight() <= getWidth() ? getHeight() : getWidth();
				setFont(new Font("Arial", Font.PLAIN, fontSize - 4));
			}

			@Override
			public void componentMoved(ComponentEvent e) {
				
			}

			@Override
			public void componentShown(ComponentEvent e) {
				
			}

			@Override
			public void componentHidden(ComponentEvent e) {
				
			}
		});
	}
	
	public void increaseMines(){
		status++;
		// TODO: if getMines() > 8, throw exception
	}
	
	private void increaseFlags(){
		while(lock.getAndSet(true));
		status += 0x10;
		lock.set(false);
		// TODO: if getFlags() > 8, throw exception
	}
	private void decreaseFlags(){
		// TODO: if getFlags() <= 0, throw exception
		while(lock.getAndSet(true));
		status -= 0x10;
		lock.set(false);
	}
	
	public void increaseOpens(){
		while(lock.getAndSet(true));
		status += 0x100;
		lock.set(false);
		// TODO: if getOpens() > 8, throw exception
	}

	public int getNeighbors(){
		return neighbors;
	}
	
	public int getMines(){
		return (status & 0xF);
	}
	
	public int getFlags(){
		return ((status & 0xF0) >> 4);
	}
	
	public int getOpens(){
		return ((status & 0xF00) >> 8);
	}
	
	public boolean isMine(){
		return (status & MINE) != 0;
	}
	
	public boolean isOpen(){
		return (status & OPEN) != 0;
	}
	
	public boolean isComplete(){
		return (status & COMPLETE) != 0;
	}
	
	public boolean isFlag(){
		return (status & FLAG) != 0;
	}
	
	public void setMine(){
		status &= MINE;
	}
	
	public void open(){
		if(isFlag() || isOpen()) return;
		while(lock.getAndSet(true));
		status |= OPEN;
		lock.set(false);
		opens++;
		if(isMine()){
			setText("M");
			JOptionPane.showMessageDialog(null, "You lose");
			return;
		}
		
		int m = getMines();
		if(m == 0){
			openNeighbors();
		}else{
			setText(Integer.toHexString(m));
			if(m == 1){
				setForeground(Color.BLACK);
			}else if(m == 2){
				setForeground(Color.BLUE);
			}else if(m == 3){
				setForeground(new Color(0, 150, 0));
			}else if(m == 4){
				setForeground(new Color(150, 0, 150));
			}else if(m == 5){
				setForeground(new Color(150, 150, 0));
			}else if(m == 6){
				setForeground(new Color(255, 100, 0));
			}else if(m == 7){
				setForeground(new Color(255, 100, 0));
			}else if(m == 8){
				setForeground(new Color(255, 0, 250));
			}
			new OpenNeighbor(this, MainWindow.deduction).start();
		}
		setBackground(Color.WHITE);
		setNeighborOpens();
		
		if(opens == empties){
			if(!isWin.getAndSet(true)) JOptionPane.showMessageDialog(null, "You win");
			//return;
		}
	}
	
	private void openNeighbors(){
		if(row == 0){
			if(column == 0){
				mw.boxes[row][column+1].open();
				mw.boxes[row+1][column].open();
				mw.boxes[row+1][column+1].open();
			}else if(column == mw.columns - 1){
				mw.boxes[row][column-1].open();
				mw.boxes[row+1][column-1].open();
				mw.boxes[row+1][column].open();
			}else{
				mw.boxes[row][column+1].open();
				mw.boxes[row][column-1].open();
				mw.boxes[row+1][column-1].open();
				mw.boxes[row+1][column].open();
				mw.boxes[row+1][column+1].open();
			}
		}else if(row == mw.rows - 1){
			if(column == 0){
				mw.boxes[row-1][column].open();
				mw.boxes[row-1][column+1].open();
				mw.boxes[row][column+1].open();
			}else if(column == mw.columns - 1){
				mw.boxes[row-1][column-1].open();
				mw.boxes[row-1][column].open();
				mw.boxes[row][column-1].open();
			}else{
				mw.boxes[row-1][column-1].open();
				mw.boxes[row-1][column].open();
				mw.boxes[row-1][column+1].open();
				mw.boxes[row][column-1].open();
				mw.boxes[row][column+1].open();
			}
		}else{
			if(column == 0){
				mw.boxes[row-1][column].open();
				mw.boxes[row-1][column+1].open();
				mw.boxes[row][column+1].open();
				mw.boxes[row+1][column].open();
				mw.boxes[row+1][column+1].open();
			}else if(column == mw.columns - 1){
				mw.boxes[row-1][column-1].open();
				mw.boxes[row-1][column].open();
				mw.boxes[row][column-1].open();
				mw.boxes[row+1][column-1].open();
				mw.boxes[row+1][column].open();
			}else{
				mw.boxes[row-1][column-1].open();
				mw.boxes[row-1][column].open();
				mw.boxes[row-1][column+1].open();
				mw.boxes[row][column-1].open();
				mw.boxes[row][column+1].open();
				mw.boxes[row+1][column-1].open();
				mw.boxes[row+1][column].open();
				mw.boxes[row+1][column+1].open();
			}
		}
	}
	
	private void setFlagTip(){
		if(!isOpen()) setFlag();
	}
	public void flagNeighbors(){
		if(row == 0){
			if(column == 0){
				mw.boxes[row][column+1].setFlagTip();
				mw.boxes[row+1][column].setFlagTip();
				mw.boxes[row+1][column+1].setFlagTip();
			}else if(column == mw.columns - 1){
				mw.boxes[row][column-1].setFlagTip();
				mw.boxes[row+1][column-1].setFlagTip();
				mw.boxes[row+1][column].setFlagTip();
			}else{
				mw.boxes[row][column+1].setFlagTip();
				mw.boxes[row][column-1].setFlagTip();
				mw.boxes[row+1][column-1].setFlagTip();
				mw.boxes[row+1][column].setFlagTip();
				mw.boxes[row+1][column+1].setFlagTip();
			}
		}else if(row == mw.rows - 1){
			if(column == 0){
				mw.boxes[row-1][column].setFlagTip();
				mw.boxes[row-1][column+1].setFlagTip();
				mw.boxes[row][column+1].setFlagTip();
			}else if(column == mw.columns - 1){
				mw.boxes[row-1][column-1].setFlagTip();
				mw.boxes[row-1][column].setFlagTip();
				mw.boxes[row][column-1].setFlagTip();
			}else{
				mw.boxes[row-1][column-1].setFlagTip();
				mw.boxes[row-1][column].setFlagTip();
				mw.boxes[row-1][column+1].setFlagTip();
				mw.boxes[row][column-1].setFlagTip();
				mw.boxes[row][column+1].setFlagTip();
			}
		}else{
			if(column == 0){
				mw.boxes[row-1][column].setFlagTip();
				mw.boxes[row-1][column+1].setFlagTip();
				mw.boxes[row][column+1].setFlagTip();
				mw.boxes[row+1][column].setFlagTip();
				mw.boxes[row+1][column+1].setFlagTip();
			}else if(column == mw.columns - 1){
				mw.boxes[row-1][column-1].setFlagTip();
				mw.boxes[row-1][column].setFlagTip();
				mw.boxes[row][column-1].setFlagTip();
				mw.boxes[row+1][column-1].setFlagTip();
				mw.boxes[row+1][column].setFlagTip();
			}else{
				mw.boxes[row-1][column-1].setFlagTip();
				mw.boxes[row-1][column].setFlagTip();
				mw.boxes[row-1][column+1].setFlagTip();
				mw.boxes[row][column-1].setFlagTip();
				mw.boxes[row][column+1].setFlagTip();
				mw.boxes[row+1][column-1].setFlagTip();
				mw.boxes[row+1][column].setFlagTip();
				mw.boxes[row+1][column+1].setFlagTip();
			}
		}
	}
	
	public boolean openNeighborTip(){
		boolean canOpen = getFlags() == getMines();
		if(canOpen){
			openNeighbors();
			while(lock.getAndSet(true));
			status |= COMPLETE;
			lock.set(false);
		}
		return canOpen;
	}
	
	private void setFlag(){
		if(isFlag()) return;
		while(lock.getAndSet(true));
		status |= FLAG;
		mw.setFlags(++flags);
		lock.set(false);
		setForeground(Color.RED);
		setText("F");
		if(row == 0){
			if(column == 0){
				mw.boxes[row][column+1].increaseFlags();
				mw.boxes[row+1][column].increaseFlags();
				mw.boxes[row+1][column+1].increaseFlags();
			}else if(column == mw.columns - 1){
				mw.boxes[row][column-1].increaseFlags();
				mw.boxes[row+1][column-1].increaseFlags();
				mw.boxes[row+1][column].increaseFlags();
			}else{
				mw.boxes[row][column+1].increaseFlags();
				mw.boxes[row][column-1].increaseFlags();
				mw.boxes[row+1][column-1].increaseFlags();
				mw.boxes[row+1][column].increaseFlags();
				mw.boxes[row+1][column+1].increaseFlags();
			}
		}else if(row == mw.rows - 1){
			if(column == 0){
				mw.boxes[row-1][column].increaseFlags();
				mw.boxes[row-1][column+1].increaseFlags();
				mw.boxes[row][column+1].increaseFlags();
			}else if(column == mw.columns - 1){
				mw.boxes[row-1][column-1].increaseFlags();
				mw.boxes[row-1][column].increaseFlags();
				mw.boxes[row][column-1].increaseFlags();
			}else{
				mw.boxes[row-1][column-1].increaseFlags();
				mw.boxes[row-1][column].increaseFlags();
				mw.boxes[row-1][column+1].increaseFlags();
				mw.boxes[row][column-1].increaseFlags();
				mw.boxes[row][column+1].increaseFlags();
			}
		}else{
			if(column == 0){
				mw.boxes[row-1][column].increaseFlags();
				mw.boxes[row-1][column+1].increaseFlags();
				mw.boxes[row][column+1].increaseFlags();
				mw.boxes[row+1][column].increaseFlags();
				mw.boxes[row+1][column+1].increaseFlags();
			}else if(column == mw.columns - 1){
				mw.boxes[row-1][column-1].increaseFlags();
				mw.boxes[row-1][column].increaseFlags();
				mw.boxes[row][column-1].increaseFlags();
				mw.boxes[row+1][column-1].increaseFlags();
				mw.boxes[row+1][column].increaseFlags();
			}else{
				mw.boxes[row-1][column-1].increaseFlags();
				mw.boxes[row-1][column].increaseFlags();
				mw.boxes[row-1][column+1].increaseFlags();
				mw.boxes[row][column-1].increaseFlags();
				mw.boxes[row][column+1].increaseFlags();
				mw.boxes[row+1][column-1].increaseFlags();
				mw.boxes[row+1][column].increaseFlags();
				mw.boxes[row+1][column+1].increaseFlags();
			}
		}
	}
	
	private void clearFlag(){
		if(!isFlag()) return;
		while(lock.getAndSet(true));
		status &= ~FLAG;
		mw.setFlags(--flags);
		lock.set(false);
		setText("");
		if(row == 0){
			if(column == 0){
				mw.boxes[row][column+1].decreaseFlags();
				mw.boxes[row+1][column].decreaseFlags();
				mw.boxes[row+1][column+1].decreaseFlags();
			}else if(column == mw.columns - 1){
				mw.boxes[row][column-1].decreaseFlags();
				mw.boxes[row+1][column-1].decreaseFlags();
				mw.boxes[row+1][column].decreaseFlags();
			}else{
				mw.boxes[row][column+1].decreaseFlags();
				mw.boxes[row][column-1].decreaseFlags();
				mw.boxes[row+1][column-1].decreaseFlags();
				mw.boxes[row+1][column].decreaseFlags();
				mw.boxes[row+1][column+1].decreaseFlags();
			}
		}else if(row == mw.rows - 1){
			if(column == 0){
				mw.boxes[row-1][column].decreaseFlags();
				mw.boxes[row-1][column+1].decreaseFlags();
				mw.boxes[row][column+1].decreaseFlags();
			}else if(column == mw.columns - 1){
				mw.boxes[row-1][column-1].decreaseFlags();
				mw.boxes[row-1][column].decreaseFlags();
				mw.boxes[row][column-1].decreaseFlags();
			}else{
				mw.boxes[row-1][column-1].decreaseFlags();
				mw.boxes[row-1][column].decreaseFlags();
				mw.boxes[row-1][column+1].decreaseFlags();
				mw.boxes[row][column-1].decreaseFlags();
				mw.boxes[row][column+1].decreaseFlags();
			}
		}else{
			if(column == 0){
				mw.boxes[row-1][column].decreaseFlags();
				mw.boxes[row-1][column+1].decreaseFlags();
				mw.boxes[row][column+1].decreaseFlags();
				mw.boxes[row+1][column].decreaseFlags();
				mw.boxes[row+1][column+1].decreaseFlags();
			}else if(column == mw.columns - 1){
				mw.boxes[row-1][column-1].decreaseFlags();
				mw.boxes[row-1][column].decreaseFlags();
				mw.boxes[row][column-1].decreaseFlags();
				mw.boxes[row+1][column-1].decreaseFlags();
				mw.boxes[row+1][column].decreaseFlags();
			}else{
				mw.boxes[row-1][column-1].decreaseFlags();
				mw.boxes[row-1][column].decreaseFlags();
				mw.boxes[row-1][column+1].decreaseFlags();
				mw.boxes[row][column-1].decreaseFlags();
				mw.boxes[row][column+1].decreaseFlags();
				mw.boxes[row+1][column-1].decreaseFlags();
				mw.boxes[row+1][column].decreaseFlags();
				mw.boxes[row+1][column+1].decreaseFlags();
			}
		}
	}
	
	private void setNeighborOpens(){
		if(row == 0){
			if(column == 0){
				mw.boxes[row][column+1].increaseOpens();
				mw.boxes[row+1][column].increaseOpens();
				mw.boxes[row+1][column+1].increaseOpens();
			}else if(column == mw.columns - 1){
				mw.boxes[row][column-1].increaseOpens();
				mw.boxes[row+1][column-1].increaseOpens();
				mw.boxes[row+1][column].increaseOpens();
			}else{
				mw.boxes[row][column+1].increaseOpens();
				mw.boxes[row][column-1].increaseOpens();
				mw.boxes[row+1][column-1].increaseOpens();
				mw.boxes[row+1][column].increaseOpens();
				mw.boxes[row+1][column+1].increaseOpens();
			}
		}else if(row == mw.rows - 1){
			if(column == 0){
				mw.boxes[row-1][column].increaseOpens();
				mw.boxes[row-1][column+1].increaseOpens();
				mw.boxes[row][column+1].increaseOpens();
			}else if(column == mw.columns - 1){
				mw.boxes[row-1][column-1].increaseOpens();
				mw.boxes[row-1][column].increaseOpens();
				mw.boxes[row][column-1].increaseOpens();
			}else{
				mw.boxes[row-1][column-1].increaseOpens();
				mw.boxes[row-1][column].increaseOpens();
				mw.boxes[row-1][column+1].increaseOpens();
				mw.boxes[row][column-1].increaseOpens();
				mw.boxes[row][column+1].increaseOpens();
			}
		}else{
			if(column == 0){
				mw.boxes[row-1][column].increaseOpens();
				mw.boxes[row-1][column+1].increaseOpens();
				mw.boxes[row][column+1].increaseOpens();
				mw.boxes[row+1][column].increaseOpens();
				mw.boxes[row+1][column+1].increaseOpens();
			}else if(column == mw.columns - 1){
				mw.boxes[row-1][column-1].increaseOpens();
				mw.boxes[row-1][column].increaseOpens();
				mw.boxes[row][column-1].increaseOpens();
				mw.boxes[row+1][column-1].increaseOpens();
				mw.boxes[row+1][column].increaseOpens();
			}else{
				mw.boxes[row-1][column-1].increaseOpens();
				mw.boxes[row-1][column].increaseOpens();
				mw.boxes[row-1][column+1].increaseOpens();
				mw.boxes[row][column-1].increaseOpens();
				mw.boxes[row][column+1].increaseOpens();
				mw.boxes[row+1][column-1].increaseOpens();
				mw.boxes[row+1][column].increaseOpens();
				mw.boxes[row+1][column+1].increaseOpens();
			}
		}
	}
	
	public void initializeMap(){
		int i, j;
		LinkedList<Integer> list = new LinkedList<Integer>();
		for(i = 0; i < mw.rows; i++){
			for(j = 0; j < mw.columns; j++){
				list.addLast((j << 8) | i);
			}
		}
		Random generator = new Random();
		for(i = 0; i < mw.mines; i++){
			int r, c;
			do{
				j = list.remove(generator.nextInt(list.size()));
				r = (j & 0xFF);
				c = (j >> 8);
			}while(r >= row - 1 && r <= row + 1 && c >= column - 1 && c <= column + 1);
			
			mw.boxes[r][c].setMine();
			if(r == 0){
				if(c == 0){
					mw.boxes[r][c+1].increaseMines();
					mw.boxes[r+1][c].increaseMines();
					mw.boxes[r+1][c+1].increaseMines();
				}else if(c == mw.columns - 1){
					mw.boxes[r][c-1].increaseMines();
					mw.boxes[r+1][c-1].increaseMines();
					mw.boxes[r+1][c].increaseMines();
				}else{
					mw.boxes[r][c+1].increaseMines();
					mw.boxes[r][c-1].increaseMines();
					mw.boxes[r+1][c-1].increaseMines();
					mw.boxes[r+1][c].increaseMines();
					mw.boxes[r+1][c+1].increaseMines();
				}
			}else if(r == mw.rows - 1){
				if(c == 0){
					mw.boxes[r-1][c].increaseMines();
					mw.boxes[r-1][c+1].increaseMines();
					mw.boxes[r][c+1].increaseMines();
				}else if(c == mw.columns - 1){
					mw.boxes[r-1][c-1].increaseMines();
					mw.boxes[r-1][c].increaseMines();
					mw.boxes[r][c-1].increaseMines();
				}else{
					mw.boxes[r-1][c-1].increaseMines();
					mw.boxes[r-1][c].increaseMines();
					mw.boxes[r-1][c+1].increaseMines();
					mw.boxes[r][c-1].increaseMines();
					mw.boxes[r][c+1].increaseMines();
				}
			}else{
				if(c == 0){
					mw.boxes[r-1][c].increaseMines();
					mw.boxes[r-1][c+1].increaseMines();
					mw.boxes[r][c+1].increaseMines();
					mw.boxes[r+1][c].increaseMines();
					mw.boxes[r+1][c+1].increaseMines();
				}else if(c == mw.columns - 1){
					mw.boxes[r-1][c-1].increaseMines();
					mw.boxes[r-1][c].increaseMines();
					mw.boxes[r][c-1].increaseMines();
					mw.boxes[r+1][c-1].increaseMines();
					mw.boxes[r+1][c].increaseMines();
				}else{
					mw.boxes[r-1][c-1].increaseMines();
					mw.boxes[r-1][c].increaseMines();
					mw.boxes[r-1][c+1].increaseMines();
					mw.boxes[r][c-1].increaseMines();
					mw.boxes[r][c+1].increaseMines();
					mw.boxes[r+1][c-1].increaseMines();
					mw.boxes[r+1][c].increaseMines();
					mw.boxes[r+1][c+1].increaseMines();
				}
			}
		}
		start = true;
	}
	
	public int getRow(){
		return row;
	}
	public int getColumn(){
		return column;
	}
}
